/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package intopo.evogame.partnerselection;

import intopo.game.Description;
import intopo.game.Game;
import intopo.game.ReadStrategy;
import intopo.game.Strategy;
import intopo.game.impl.givetake.GiveTakeDeterministicStrategy;
import intopo.game.impl.givetake.GiveTakeGame;
import intopo.game.impl.givetake.GiveTakeStrategy;
import intopo.game.impl.givetake.GiveTakeDescriptionTextGUI;
import intopo.game.impl.givetake.ReadGiveTakeDeterministicStrategy;
import java.io.BufferedInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.util.Scanner;

/**
 * Read the data produced by a simulation and create some graphics.  We assume that all strategies instances belong to the same class.  Therefore, the file that represents the players' strategies is randomly accessed.  The record size is constant.  If multiple classes are used, the part of the record that represents the strategy must distinguish between the different classes.
 *
 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Mariano</a>
 *
 * @version 1.0 __DATE__
 */
public class Statistics<G extends Game<S>, S extends Strategy<S>>
{
	final private Description<G, S> description;
	final Class<? extends S> strategyClass;
	/**
	 * The object used to read strategies.  We assume that all strategies instances belong to the same class.  Therefore, the file that represents the players' strategies is randomly accessed.  The record size is constant.  If multiple classes are used, the part of the record that represents the strategy must distinguish between the different classes.
	 */
	final ReadStrategy<S> readStrategy;
	/**
	 * Each index represents a strategy bin.  Each position contains the number of times some player played with another player (with the same of different strategy).
	 */
	final int[][] gameCount;
	/**
	 * Number of times a strategy selected another to play.  Each index represents a strategy bin.
	 * Position {@code [i][j]} contains the number of times a player with strategy bin {@code i} selected a partner with strategy bin {@code j} to play.
	 */
	final int[][] gameSelect;


	Statistics (Description<G, S> description, Class<? extends S> strategyClass, ReadStrategy<S> readStrategy, int numberBins)
	{
		this.description = description;
		this.strategyClass = strategyClass;
		this.readStrategy = readStrategy;
		this.gameCount = new int [numberBins][numberBins];
		this.gameSelect = new int [numberBins][numberBins];
	}
	/**
	 * Count the number of times each strategy selected another for playing.
	 */
	private void strategyCount ()
		throws FileNotFoundException, IOException
	{
		RandomAccessFile chromosomeRAF = new RandomAccessFile ("chromosome.tab", "r");
		RandomAccessFile playerRAF = new RandomAccessFile ("player.tab", "r");
		DataInputStream gamesSelectedDIS =
			new DataInputStream (
				new BufferedInputStream (
					new FileInputStream ("games-selected.tab"),
					4096));
		ChromosomeRecord rc = new ChromosomeRecord (this.readStrategy);
		PlayerRecord pr = new PlayerRecord ();
		GamesSelectRecord gsr = new GamesSelectRecord ();
		// read the raw data
		try {
			while (true) {
				int binSelected, binSelecting;
				gsr.read (gamesSelectedDIS);
				//
				pr.read (playerRAF, gsr.refIDSelectedPlayer);
				rc.read (chromosomeRAF, pr.refIDChromosome, this.readStrategy);
//				System.out.println ("1»» " + rc);
				binSelected = rc.strategy.getBin ();
				if (binSelected >= this.gameCount.length) {
					System.out.print ("strategy: ");
					System.out.println (rc);
					System.out.print ("    bin1: ");
					System.out.println (binSelected);
				}
				//
				pr.read (playerRAF, gsr.refIDSelectingPlayer);
				rc.read (chromosomeRAF, pr.refIDChromosome, this.readStrategy);
//				System.out.println ("2»» " + rc);
				binSelecting = rc.strategy.getBin ();
				if (binSelecting >= this.gameCount.length) {
					System.out.print ("strategy: ");
					System.out.println (rc);
					System.out.print ("    bin2: ");
					System.out.println (binSelecting);
				}
				if (binSelecting > binSelected) {
					this.gameCount [binSelecting][binSelected]++;
				}
				else {
					this.gameCount [binSelected][binSelecting]++;
				}
				this.gameSelect [binSelecting][binSelected]++;
			}
		}
		catch (EOFException e) {

		}
		gamesSelectedDIS.close ();
		playerRAF.close ();
		chromosomeRAF.close ();
		// write the processed data
		PrintStream ps;
		// graph with number of times two strategies played together
		ps = new PrintStream (new File ("game-count.dot"));
		ps.println ("graph {");
		for (int b1 = this.gameCount.length - 1; b1 >= 0; b1--) {
			boolean hasLinks = false;
			for (int b2 = b1; b2 >= 0; b2--) {
				if (this.gameCount [b1][b2] > 0) {
					ps.format ("  b%d -- b%d [label = \"%d\"];\n",
						b1, b2, this.gameCount [b1][b2]);
					hasLinks = true;
				}
			}
			for (int b2 = this.gameCount.length - 1; b2 > b1 && !hasLinks; b2--) {
				if (this.gameCount [b2][b1] > 0) {
					hasLinks = true;
				}
			}
			if (hasLinks) {
				ps.format ("  b%d [label =\"%s\"];\n",
					b1, this.description.getBinDescription (this.strategyClass, b1));
			}
		}
		ps.println ("}");
		ps.close ();
		// graph with number of times a player with some strategy selected another strategy
		ps = new PrintStream (new File ("game-select.dot"));
		ps.println ("digraph {");
		for (int b1 = this.gameSelect.length - 1; b1 >= 0; b1--) {
			boolean hasLinks = false;
			for (int b2 = this.gameSelect.length - 1; b2 >= 0; b2--) {
				if (this.gameSelect [b1][b2] > 0) {
					ps.format ("  b%d -> b%d [label = \"%d\"];\n",
						b1, b2, this.gameSelect [b1][b2]);
					hasLinks = true;
				}
			}
			if (hasLinks) {
				ps.format ("  b%d [label =\"%s\"];\n",
					b1, this.description.getBinDescription (this.strategyClass, b1));
			}

		}
		ps.println ("}");
		ps.close ();
		// call graphviz tools
		this.runProcess (new String[] {"/usr/bin/neato", "-Tpng", "game-count.dot", "-o", "game-count.png"});
		this.runProcess (new String[] {"/usr/bin/dot", "-Tpng", "game-select.dot", "-o", "game-select.png"});
	}
	private void runProcess (String[] cmdarray)
		throws IOException
	{
		PrintStream ps;
		Runtime runtime = Runtime.getRuntime ();
		Process neatoProcess = runtime.exec (cmdarray);
		try {
			if (neatoProcess.waitFor () == 0) {
				ps = System.out;
			}
			else {
				ps = System.err;
			}
			Scanner scanner = new Scanner (neatoProcess.getInputStream ());
			System.out.format ("-- begin output %s --\n", cmdarray [0]);
			while (scanner.hasNextLine ()) {
				ps.println (scanner.nextLine ());
			}
			System.out.println ("-- end output --");
		}
		catch (InterruptedException ex) {
		}
	}
	static public void main (String[] args) throws FileNotFoundException, IOException
	{
		String helpMessage = "Usage:java -jar GAME OPTIONS\nwhere GAME is\n\tgtd   give take game with deterministic strategies\n";
		if (args.length < 1) {
			System.err.println ("Invalid number of arguments.\n" + helpMessage);
			System.exit (1);
			return ;
		}
		Statistics stat;
		int numberBins;
		if (args [0].equals ("gtd") && args.length == 3) {
			GiveTakeDeterministicStrategy.Parameters.giveIteration = Integer.parseInt (args [1]);
			GiveTakeDeterministicStrategy.Parameters.takeIteration = Integer.parseInt (args [2]);
			ReadStrategy<GiveTakeStrategy> readStrategy;
			readStrategy = new ReadGiveTakeDeterministicStrategy ();
			numberBins =
				(GiveTakeDeterministicStrategy.Parameters.giveIteration + 1)
				* (GiveTakeDeterministicStrategy.Parameters.takeIteration + 1);
			stat = new Statistics<GiveTakeGame, GiveTakeStrategy> (
					 GiveTakeDescriptionTextGUI.getInstance (),
					 GiveTakeDeterministicStrategy.class,
					 readStrategy,
					 numberBins);
		}
		else {
			System.err.println ("Unknown argument.\n" + helpMessage);
			System.exit (1);
			return ;
		}
		try {
			stat.strategyCount ();
		}
		catch (Exception e) {
			System.err.println ("Error counting games\n" + e.getMessage ());
			e.printStackTrace ();
		}
	}
}
/**
 * Base class of all records belong to random access files.  These records have a unique primary key stored as a Java int.
 * This class provides a method that given a random access file and a record key, positions the file at the place where the record is.  This assumes the records are written to the file in ascending order.
 * @author pedro
 */
class Record
{
	/**
	 * Size in bytes of record.  
	 */
	final private int recordSize;
	int ID;
	protected Record (int recordSize)
	{
		this.recordSize = recordSize;
	}
	protected boolean position (RandomAccessFile raf, int id)
		throws IOException
	{
//		System.out.println ("seeking to " + id * this.recordSize);
		raf.seek (id * this.recordSize);
		ID = raf.readInt ();
		if (ID == id) {
			return true;
		}
		long filePointer = raf.getFilePointer ();
		System.err.println (id + " is not at " + filePointer);
		while (ID < id) {
			filePointer += recordSize;
			raf.seek (filePointer);
			ID = raf.readInt ();
		}
		while (ID > id) {
			filePointer -= recordSize;
			raf.seek (filePointer);
			ID = raf.readInt ();
		}
		return ID == id;
	}
}
/**
 *
 * @author pedro
 * @param <S>
 */
class ChromosomeRecord<S extends Strategy<S>>
	extends Record
{
	byte poolSize;
	/**
	 * Utility threshold used in the decision to update the probability and
	 * combination vectors.  This field represents parameter
	 * <i>u<sub>t</sub></i> mentioned in the paper.
	 *
	 * <p><b>NOTE:</b> In this implementation, utility is equal to the
	 * payoff obtained by the player in the game.
	 */
	float utilityThreshold;
	/**
	 * Multiplication factor used when a probability is decreased because
	 * the player obtained a utility lower than {@code utilityThreshold}.
	 * This field represents parameter <i>&delta;</i> mentioned in the
	 * paper.
	 */
	float delta;
	/**
	 * Number of bits used in encoding probabilities of the probability vector.  Valid values range from 1 to 8.
	 */
	byte bitsPerProbability;
	S strategy;
	/**
	 *
	 * @param read
	 */
	ChromosomeRecord (ReadStrategy<S> read)
	{
		super (14 + read.strategySize ());
	}
	/**
	 *
	 * @param dis
	 * @param read
	 * @throws java.io.IOException
	 */
	ChromosomeRecord (DataInputStream dis, ReadStrategy<S> read)
		throws IOException
	{
		this (read);
		this.read (dis, read);
	}
	/**
	 *
	 * @param raf
	 * @param id
	 * @param read
	 * @throws java.io.IOException
	 */
	ChromosomeRecord (RandomAccessFile raf, int id, ReadStrategy<S> read)
		throws IOException
	{
		this (read);
		this.read (raf, id, read);
	}
	/**
	 * Read a record with the given key from the {@code chromosome} table.  Records in this table are sorted by the primary key.  Whenever a new chromosome is constructed, it is written to this table.
	 * <p> This method assumes that all records have the same strategy.  This means record size is constant.
	 * @param raf  The file where the table is stored
	 * @param id  The primary key of the record to read
	 * @param read   The object used to read a strategy.
	 * @throws java.io.IOException
	 * @see intopo.evogame.partnerselection.chromosome.Chromosome
	 * @see ReadStrategy#strategySize() 
	 */
	void read (RandomAccessFile raf, int id, ReadStrategy<S> read)
		throws IOException
	{
//		final int recordSize = 14 + read.strategySize ();
//		long filePointer = raf.getFilePointer ();
//		int anID;
//		anID = raf.readInt ();
//		while (anID < id) {
//			filePointer += recordSize;
//			raf.seek (filePointer);
//			anID = raf.readInt ();
//		}
//		while (anID > id) {
//			filePointer -= recordSize;
//			raf.seek (filePointer);
//			anID = raf.readInt ();
//		}
//		this.ID = id;
		if (!this.position (raf, id)) {
			throw new IOException ("Could not find chromosome record with id " + id);
		}
		this.readRest (raf, read);
	}
	/**
	 * Read the next record from the chromosome table.
	 * @param di  The file where the table is stored.
	 * @param read  The object used to read a strategy.
	 * @throws java.io.IOException
	 */
	void read (DataInput di, ReadStrategy<S> read)
		throws IOException
	{
		this.ID = di.readInt ();
		this.readRest (di, read);
	}
	private void readRest (DataInput ri, ReadStrategy<S> read)
		throws IOException
	{
		this.poolSize = ri.readByte ();
		this.utilityThreshold = ri.readFloat ();
		this.delta = ri.readFloat ();
		this.bitsPerProbability = ri.readByte ();
		this.strategy = read.read (ri);
	}
	@Override
	public int hashCode ()
	{
		return this.ID;
	}
	@Override
	public boolean equals (Object other)
	{
		return this.ID == ((ChromosomeRecord<S>) other).ID;
	}
	public String toString ()
	{
		return String.format ("%d l%d δ%f S%s", this.ID, this.poolSize, this.delta, this.strategy.toString ());
	}
}

/**
 * Represents a record in the {@code Player} table.  Data to this table is written in methods {@code AbstractPlayer#writePlayer(int, int)} and {@code AbstractPlayer#writePlayer(int, int, int)}.  The output stream associated with the file is {@code Database#player}.  See the documentation of this field for the format of a record.
 * <p> This class provides methods to access a record of this table by its primary key.  This key is the player's identification.  The constructor of class {@code Player} writes a new record.  Therefore a single seek is suficient to obtain a record given the primary key.
 *
 * <p> Field {@code Record.ID} represents player's identification.  Corresponds to field {@code AbstractPlayer#ID}.  Primary key of the table.
 *
 * @see intopo.evogame.partnerselection.player.AbstractPlayer#ID
 *
 * @author pedro
 * @see intopo.evogame.partnerselection.player.AbstractPlayer#writePlayer(int, int)
 * @see intopo.evogame.partnerselection.player.AbstractPlayer#writePlayer(int, int, int)
 * @see intopo.evogame.partnerselection.Database#player
 */
final class PlayerRecord
	  extends Record
{
	/**
	 * Reference to the chromosome of this player.
	 */
	int refIDChromosome;
	int refIDNode;
	int refIDParent;
	int bornTime;
	int deathTime;
	PlayerRecord ()
	{
		super (24);
	}
	PlayerRecord (DataInputStream dis)
		throws IOException
	{
		this ();
		this.read (dis);
	}
	PlayerRecord (RandomAccessFile raf, int id)
		throws IOException
	{
		this ();
		this.read (raf, id);
	}
	void read (RandomAccessFile raf, int id)
		throws IOException
	{
//		final int recordSize = 20;
//		long filePointer = raf.getFilePointer ();
//		int anID;
//		anID = raf.readInt ();
//		while (anID < id) {
//			filePointer += recordSize;
//			raf.seek (filePointer);
//			anID = raf.readInt ();
//		}
//		while (anID > id) {
//			filePointer -= recordSize;
//			raf.seek (filePointer);
//			anID = raf.readInt ();
//		}
//		this.ID = id;
		super.position (raf, id);
		this.readRest (raf);
	}
	void read (DataInput di)
		throws IOException
	{
		this.ID = di.readInt ();
		readRest (di);
	}
	private void readRest (DataInput di)
		throws IOException
	{
		this.refIDChromosome = di.readInt ();
		this.refIDNode = di.readInt ();
		this.refIDParent = di.readInt ();
		this.bornTime = di.readInt ();
		this.deathTime = di.readInt ();
	}
	@Override
	public int hashCode ()
	{
		return this.ID;
	}
	@Override
	public boolean equals (Object other)
	{
		return this.ID == ((PlayerRecord) other).ID;
	}
}

class GamesSelectRecord
{
	int time;
	int refIDSelectingPlayer;
	int refIDSelectedPlayer;
	GamesSelectRecord ()
	{
		
	}
	GamesSelectRecord (DataInputStream dis)
		throws IOException
	{
		this.read (dis);
	}
	void read (DataInputStream dis)
		throws IOException
	{
		this.time = dis.readInt ();
		this.refIDSelectingPlayer = dis.readInt ();
		this.refIDSelectedPlayer = dis.readInt ();
	}
	@Override
	public int hashCode ()
	{
		return this.time + this.refIDSelectingPlayer;
	}
	@Override
	public boolean equals (Object other)
	{
		GamesSelectRecord gsr = (GamesSelectRecord) other;
		return this.time == gsr.time
			&& this.refIDSelectedPlayer == gsr.refIDSelectedPlayer
			&& this.refIDSelectingPlayer == gsr.refIDSelectingPlayer;
	}
	public String toString ()
	{
		return String.format ("r%d p%d p%d", this.time, this.refIDSelectingPlayer, this.refIDSelectedPlayer);
	}
}

// Local Variables:
// mode: java
// mode: flyspell-prog
// ispell-local-dictionary: "british"
// End:
